/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.test_instantiator;

import picounit.Invokable;
import picounit.Lifecycle;
import picounit.features.signature_factory.Signature;
import picounit.features.signature_factory.SignatureFactory;
import picounit.features.test_factory.LifecycleInvocationArgumentHandler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Invoker {
	private final LifecycleInvocationArgumentHandler lifecycleInvocationArgumentHandler;
	private final SignatureFactory signatureFactory;

	public Invoker(SignatureFactory signatureFactory) {
		this.signatureFactory = signatureFactory;
		this.lifecycleInvocationArgumentHandler = new LifecycleInvocationArgumentHandler(this);
	}

	public void invoke(String name, Object target)
		throws IllegalAccessException, InvocationTargetException {

		invoke(name, target, target.getClass(), lifecycleInvocationArgumentHandler);
	}
	
	public void invoke(String name, Object target, InvocationArgumentHandler argumentHandler)
		throws IllegalAccessException, InvocationTargetException {
	
		invoke(name, target, target.getClass(), argumentHandler);
	}

	public void invoke(Method method, Object target) throws IllegalArgumentException,
		IllegalAccessException, InvocationTargetException {

		invoke(method, target, lifecycleInvocationArgumentHandler);
	}

	public void invoke(Method method, Object target, InvocationArgumentHandler argumentHandler)
		throws IllegalAccessException, InvocationTargetException {

		boolean accessible = method.isAccessible();

		method.setAccessible(true);

		try {
			Signature signature = signatureFactory.create(method);

			Object[] arguments = signature.parameterValues();

			argumentHandler.preInvocation(arguments);

			postInvocation(method.invoke(target, arguments));

			argumentHandler.postInvocation(arguments);
		}
		finally {
			method.setAccessible(accessible);
		}
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private void postInvocation(Object result) throws IllegalAccessException,
		InvocationTargetException {

		if (result instanceof Lifecycle) {
			invoke("setUp", result);
		}

		if (result instanceof Invokable) {
			Invokable invokable = (Invokable) result;

			invokable.invoke();
		}

		if (result instanceof Lifecycle) {
			invoke("tearDown", result);
		}
	}

	private void invoke(String name, Object target, Class targetClass,
		InvocationArgumentHandler argumentHandler)
			throws IllegalAccessException, InvocationTargetException {

		Class superClass = targetClass.getSuperclass();
		if (!superClass.equals(Object.class)) {
			invoke(name, target, superClass, argumentHandler);
		}

		Method zeroArg = getZeroArgMethod(targetClass, name);

		if (zeroArg != null) {
			invoke(zeroArg, target, argumentHandler);
		}

		for (Method method : targetClass.getDeclaredMethods()) {
			if (method.equals(zeroArg)) {
				continue;
			}

			if (method.getName().equals(name)) {
				invoke(method, target, argumentHandler);
			}									   
		}
	}

	private Method getZeroArgMethod(Class targetClass, String name) {
		try {
			return targetClass.getDeclaredMethod(name, new Class[0]);
		}
		catch (SecurityException e) {
			return null;
		}
		catch (NoSuchMethodException e) {
			return null;
		}
	}
}
